package inglar.autotc.server.daemon;

import java.util.Vector;

import inglar.autotc.server.Database;
import inglar.autotc.server.Host;
import inglar.autotc.shared.Values;
import inglar.autotc.shared.db.Actions;
import inglar.autotc.shared.db.Clients;
import inglar.autotc.shared.db.Templates;

public class Daemon implements Runnable {
	
	private Thread thread = null;
	private boolean ALIVE = true;
	
	private String dbhost = null;
	private String dbport = null;
	private String dbuser = null;
	private String dbpass = null;
	
	Database db = null;

	/*
	 * open configuration files, create a trace file, create ServerSockets, Threads
	 */
	public void init(String[] args) {
		dbhost = System.getProperty(Values.ENVNAME_JAVA_DAEMON_DBHOST, Values.DAEMON_DEFAULT_DBHOST);
		System.out.println(Values.ENVNAME_JAVA_DAEMON_DBHOST + ": " + dbhost);
		
		dbport = System.getProperty(Values.ENVNAME_JAVA_DAEMON_DBPORT, Values.DAEMON_DEFAULT_DBPORT);
		Integer v = null;
		try { v = new Integer(dbport); } catch (Throwable cause) { cause.printStackTrace(); }
		if(v == null) v = new Integer(Values.DAEMON_DEFAULT_DBPORT);
		System.out.println(Values.ENVNAME_JAVA_DAEMON_DBPORT + ": " + dbport);
		
		dbuser = System.getProperty(Values.ENVNAME_JAVA_DAEMON_DBUSER, Values.DAEMON_DEFAULT_DBUSER);
		System.out.println(Values.ENVNAME_JAVA_DAEMON_DBUSER + ": " + dbuser);
		
		dbpass = System.getProperty(Values.ENVNAME_JAVA_DAEMON_DBPASS, Values.DAEMON_DEFAULT_DBPASS);
		System.out.println(Values.ENVNAME_JAVA_DAEMON_DBPASS + ": " + dbpass);
		
		thread = new Thread(this);
	}
	
	/*
	 * start the Thread, accept incoming connections
	 */
	public void start() {
		thread.start();
		log("autotc daemon started");
	}
	
	/*
	 *  inform the Thread to terminate the run(), close the ServerSockets
	 */
	public void stop() {
		this.ALIVE = false;
		log("stopping autotc daemon...");
	}
	
	/*
	 * destroy any object created in init()
	 */
	public void destroy() {
		thread = null;
		log("autotc daemon stopped");
	}
	
	@Override
	public void run() {
		
		while(ALIVE) {
			
			Host.out("");
			
			try {
				Thread.sleep(Values.DAEMON_TIMEOUT);
			} catch (InterruptedException e1) {
				e1.printStackTrace();
				continue;
			}
			
			// try to make a database connection 
			if(db == null) {
				try {
					db = new Database(dbhost, dbport, dbuser, dbpass);
				} catch (Exception e) {
					errlog("error creating DAO \n", e);
//					Host.err(Host.throwableDump(e));
					continue;
				}
			}
			
			// wait until userhook.action is null
			try {
				if(db.userHasIssuedCommand()) {
					log("user is changing tc scripts, waiting until it's done");
					continue;
				}
			} catch (Exception e) {
				errlog("error checking userhook", e);
				continue;
			}
			
			Actions[] actions = null;
			
			// get actions
			try {
				actions = db.getActions();
				if(actions.length == 0) {
					log("no actions in database");
					continue;
				}
			} catch(Exception e) {
				errlog("error retrieving `actions`", e);
				continue;
			}
			
			Clients[] clients = null;
			
			// get clients
			try {
				clients = db.getClients();
				if(clients.length == 0) {
					log("no clients in database.");
					continue;
				}
			} catch (Exception e) {
				errlog("error retrieving `clients`", e);
				continue;
			}
			
			Actions applyAction = null;
			
			// netstat each client
			for (int i = 0; i < clients.length; i++) {
				Integer port = null;
				try {
					port = new Integer(clients[i].getPort());
				} catch (NumberFormatException nfe) {
					continue;
				}
				String ip = clients[i].getAddress();
				Vector<String> n = Loader.getClientConnections(ip, port);
				
				// given netstat data n, check action with highest priority value 
				Actions a = findAction(actions, n);
				if(a == null) continue;
				if(applyAction == null) applyAction = a;
				else {
					int currPrio = Integer.parseInt(applyAction.getPrio());
					int nextPrio = Integer.parseInt(a.getPrio());
					if(currPrio < nextPrio) applyAction = a;
				}
			}
			
			if(applyAction == null) {
				log("no action to be applied");
				continue;
			}
			
			log("action to apply: " + applyAction);
			
			String device = null;
			String tname = null;
			
			String set = applyAction.getAction().trim();
			int idxOpenpar = set.lastIndexOf('(');
			int idxClosepar = set.lastIndexOf(')');
			if((idxOpenpar * idxClosepar) < 0) continue; // one or more parentheses is missing
			if(idxOpenpar >= idxClosepar) continue; // bad parentheses order
			
			device = set.substring(idxOpenpar + 1, idxClosepar).trim();
			if(device.isEmpty()) continue; // no device listing
			
			// "set tname (device)"
			//  012345
			tname = set.substring(4, idxOpenpar).trim();
			
			Templates template = null;
			try {
				template = db.getTemplate(device, tname);
			} catch (Exception e) {
				errlog("error retrieving `templates`", e);
				continue;
			}
			
			if(template == null) {
				log("no template for this action");
				continue;
			}
			
			System.out.println("template to apply: " + template);
			try {
				applyTemplate(template);
			} catch (Exception e) {
				errlog("unable to apply template", e);
				continue;
			}
			
//			log("next pass . . .");
		}	
	}
	
	private void applyTemplate(Templates template) {
		Loader.setScript(template.getDevice(), template.getId(), "0", Host.timeStamp(), template.getScript());
	}

	private Actions findAction(Actions[] actions, Vector<String> n) {
		int size = n.size();
		if(size < 3) {
//			log("Socket call {netstat} does not return any useful data.");
			return null;
		}
		
		final int idxLocal = 3;
		final int idxForeign = 4;
		Actions retval = null;
		int prio = -1;
		
		for(int i = 0; i < actions.length; i++) {
			
			if(Integer.parseInt(actions[i].getPrio()) < prio) continue;
			
			int matches = 0;
			
			int numDetects = actions[i].getDetectRows();
			for(int j = 0; j < numDetects; j++) {
				String dval = actions[i].getDetectValue(j);
				String dopt = actions[i].getDetectOption(j);
				
				for(int k = 2; k < size; k++) {
					String line = n.get(k);
					String[] cols = line.split(Values.REGEX_ALL_WHITESPACE);
					
					String socket = null;
					if(dopt.startsWith("local")) {
						socket = cols[idxLocal];
					} else if(dopt.startsWith("foreign")) {
						socket = cols[idxForeign];
					}
					
					String match = null;
					if(dopt.endsWith("ip")) {
						match = getAddress(socket);
					} else if(dopt.endsWith("ip:port")) {
						match = socket;
					} else if(dopt.endsWith("port")) {
						match = getPort(socket);
					}
					
					if(match == null) continue;
					if(match.isEmpty()) continue;
					
					if(match.equals(dval)) {
						matches++;
						break;
					}
				}
			}
			
			if(matches == actions[i].getDetectRows()) {
				retval = actions[i];
				prio = Integer.parseInt(actions[i].getPrio());
			}
		}
		return retval;
	}
	
	String getAddress(String nsocket) {
		if(nsocket == null) return null;
		int lastColon = nsocket.lastIndexOf(':');
		if(lastColon < 0) return null; // has no address
		else return nsocket.substring(0, lastColon);
	}
	
	String getPort(String nsocket) {
		if(nsocket == null) return null;
		int lastColon = nsocket.lastIndexOf(':');
		if(lastColon < 0) return null; // has no address
		else return nsocket.substring(lastColon + 1);
	}

	void log(String msg) {
		System.out.println("LOG " + Host.timeStamp() + " : " + msg);
	}
	
	void errlog(String errmsg, Throwable caught) {
		System.err.println("ERR " + Host.timeStamp() + " : " + errmsg.trim() + "\n" + Host.throwableDump(caught));
		
	}
}
